/* -*-C++-*- */

/** \defgroup irdefs Intermediate Representation */
/** \addtogroup irdefs
  * @{
  */

/*
   This file contains the base classes needed for the compiler IR:
   - all interfaces
   - most basic abstract classes
   - some simple leaf classes used frequently
*/

#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "frontends/common/constantParsing.h"
#include "ir/annotations.h"

/// a value that can be evaluated at compile-time
interface CompileTimeValue {
    bool equiv(const CompileTimeValue& other) const {
        return this->getNode()->equiv(*other.getNode());
    }
}

/// Base class for P4 types
abstract Type {
    static const cstring minSizeInBits;
    static const cstring minSizeInBytes;
    static const cstring maxSizeInBits;
    static const cstring maxSizeInBytes;
#emit
    typedef Type_Unknown        Unknown;
    typedef Type_Boolean        Boolean;
    typedef Type_Bits           Bits;
    typedef Type_String         String;
    typedef Type_Varbits        Varbits;
    typedef Type_Void           Void;
#end
    /// Well-defined only for types with fixed width
    virtual int width_bits() const { BUG("width_bits() on type with unknown size: %1%", this); }
    /// When possible returns the corresponding type that can be inserted
    /// in a P4 program; may return a Type_Name
    virtual const Type* getP4Type() const = 0;
}

/// Implemented by all types that may be generic:
/// Control, Extern, Method, Package, and Parser
interface IMayBeGenericType {
    /// Allows the retrieval of type parameters
    virtual const TypeParameters* getTypeParameters() const = 0;
}

/// Implemented by objects with an 'apply' method: Parser, Control, Table
interface IApply {
    static const cstring applyMethodName;
    /// @returns the type signature of the apply method
    virtual const Type_Method* getApplyMethodType() const = 0;
    virtual const ParameterList* getApplyParameters() const = 0;
}

/// base class for namespaces
interface INamespace {
    virtual Util::Enumerator<IDeclaration> *getDeclarations() const = 0;
}

/// Does not allow two declarations with the same name
interface ISimpleNamespace : INamespace {
    virtual IDeclaration getDeclByName(cstring name) const = 0;
    virtual IDeclaration getDeclByName(std::string_view name) const = 0;
}

/// A general namespace can have multiple declarations with the same name
/// E.g., an extern can have multiple methods with the same name.
interface IGeneralNamespace : INamespace {
    virtual Util::Enumerator<IDeclaration>* getDeclsByName(cstring name) const;
    /// prints an error if it finds duplicate names
    void checkDuplicateDeclarations() const;
    validate{ checkDuplicateDeclarations(); }
}

// A "namespace" that really consists of several nested namespaces
// Note that it might also contain a Simple or General namespace nested in these
interface INestedNamespace : INamespace {
    virtual std::vector<INamespace> getNestedNamespaces() const = 0;
    Util::Enumerator<IDeclaration> *getDeclarations() const override;
}

/// Interface implemented by something that can be called
/// like a function.
interface IFunctional {
    /// The parameters of the functional object
    virtual ParameterList getParameters() const = 0;
    /// Returns true if the parameters can be matched with the
    /// supplied arguments.
    bool callMatches(Vector<Argument> arguments) const;
}

/// Implemented by things that look like type variables
interface ITypeVar {
    virtual cstring getVarName() const = 0;
    inline Type asType() const { return to<Type>(); }
    virtual int getDeclId() const = 0;
}

/// Implemented by P4Parser, P4Control and Type_Package
interface IContainer : IMayBeGenericType, IDeclaration, IFunctional {
    virtual Type getType() const = 0;
    /// The type of the constructor as a method
    virtual Type_Method getConstructorMethodType() const = 0;
    virtual ParameterList getConstructorParameters() const = 0;
    // The IFunctional interface
    ParameterList getParameters() const override { return getConstructorParameters(); }
}

/// This represents a primitive type
/// (called base type in the spec)
abstract Type_Base : Type {
    const Type* getP4Type() const override { return this; }
}

/// This is needed by Expression
class Type_Unknown : Type_Base {
#nodbprint
    static Type_Unknown get();
    static Type_Unknown get(const Util::SourceInfo &si);
    toString{ return "Unknown type"_cs; }
}

/// A statement or a declaration
abstract StatOrDecl {}

/// Two declarations with the same name are not necessarily the same declaration.
/// That's why declid is used to distinguish them.
abstract Declaration : StatOrDecl, IDeclaration {
    ID          name;
    long declid = nextId++;
    ID getName() const override { return name; }
    equiv { return name == a.name; /* ignore declid */ }
 private:
    static long nextId;
 public:
    toString { return externalName(); }
}

/// A declaration which introduces a type.
/// Two declarations with the same name are not the same declaration
/// That's why declid is used to distinguish them.
/// (We don't use multiple inheritance, so we can't
/// inherit both Type and Declaration.)
abstract Type_Declaration : Type, IDeclaration {
    ID name;
    long declid = nextId++;
    ID getName() const override { return name; }
    equiv { return name == a.name; /* ignore declid */ }
 private:
    static long nextId;
 public:
    toString { return externalName(); }
    const Type* getP4Type() const override { return new Type_Name(name); }
}

/// base class for expressions
abstract Expression {
    /// Note that the type field is not visited.
    /// Most P4_16 passes don't use this field.
    /// It is a used to hold the result of TypeInferencing for the expression.
    /// It is used by the P4_14 front-end and by some back-ends.
    /// It is not visited by the visitors by default (can be visited explicitly in preorder)
    optional Type type = Type::Unknown::get();
    visit_children { (void)v; }
#apply
}

abstract Operation : Expression {
    virtual int getPrecedence() const = 0;
    virtual cstring getStringOp() const = 0;
#emit
    typedef Operation_Unary Unary;
    typedef Operation_Binary Binary;
    typedef Operation_Relation Relation;
#end
    toString{ return getStringOp(); }
}

/// Currently paths can be absolute (starting with a dot) or relative
/// (just an identifier).  In a previous design paths could have
/// multiple components.
class Path {
    ID   name;
    optional bool absolute = false;
    Path { if (!srcInfo) srcInfo = name.srcInfo; }
    inline bool isDontCare() const { return name.isDontCare(); }
    toString{
        // This is the ORIGINAL name the user used
        return absl::StrCat(absolute ? "." : "", name.toString());
    }
    inline cstring asString() const {
        // The CURRENT internal name
        return absl::StrCat(absolute ? "." : "", name);
    }
    dbprint { out << name; }
    validate { BUG_CHECK(!name.name.isNullOrEmpty(), "Empty path"); }
}

/// Handy class used in several NamedMaps
class NamedExpression : Declaration {
    Expression expression;
}

/// A token in an unparsed annotation.
/// This should really be P4::P4Parser::symbol_type, but p4parser.hpp depends
/// on the IR in a way that makes it difficult to #include in this file.
class AnnotationToken {
    int token_type;  // P4Parser::token_type in disguise.
    cstring text;
    optional NullOK UnparsedConstant* constInfo = nullptr;
    dbprint { out << text; }
}

/// Annotations are used to provide additional information to the compiler
/// Most P4 entities can be optionally annotated
class Annotation {
    ID name;

#emit
    using UnparsedAnnotation = IR::Vector<IR::AnnotationToken>;
    using ExpressionAnnotation = IR::Vector<IR::Expression>;
    using KVAnnotation = IR::IndexedVector<IR::NamedExpression>;
#end
    Annotation { if (!srcInfo) srcInfo = name.srcInfo; }

    /// For annotations parsed from P4-16 source.
    inline Annotation(Util::SourceInfo si, ID n, const Vector<AnnotationToken> &a)
        : Node(si), name(n), body(a), structured(false) {}
    // Used by JSON loader
    inline Annotation(Util::SourceInfo si, ID n, const Vector<AnnotationToken> &a, bool structured)
        : Node(si), name(n), body(a), structured(structured) {}

    // The remaining constructors are for compiler-generated annotations.
    inline Annotation(Util::SourceInfo si, ID n,
                      std::initializer_list<const Expression *> a, bool structured = false)
        : Node(si), name(n), body(a), structured(structured) {}
    inline Annotation(Util::SourceInfo si, ID n,
                      const Expression *a, bool structured = false)
        : Node(si), name(n), body(), structured(structured) {
        body.emplace<ExpressionAnnotation>(a);
    }
    inline Annotation(Util::SourceInfo si, ID n, const IR::Vector<Expression> &a, bool structured = false)
        : Node(si), name(n), body(a), structured(structured) {}
    inline Annotation(Util::SourceInfo si, ID n, const IndexedVector<NamedExpression> &kv,
                      bool structured = false)
        : Node(si), name(n), body(kv), structured(structured) {}
    inline Annotation(ID n, const Expression *a, bool structured = false)
        : name(n), body(), structured(structured) {
        body.emplace<ExpressionAnnotation>(a);
    }
    inline Annotation(ID n, std::initializer_list<const Expression *> a, bool structured = false)
        : name(n), body(a), structured(structured) {}
    inline Annotation(ID n, const IR::Vector<Expression> &a, bool structured = false)
        : name(n), body(a), structured(structured) {}
    Annotation(ID n, intmax_t v, bool structured = false)
        : name(n), structured(structured) {
        body.emplace<ExpressionAnnotation>(new Constant(v));
    }
    // Cannot use delegating ctors are ir-generator does not support initializer lists
    //  : Annotation(n, { new Constant(v) }, structured) { }
    Annotation(ID n, cstring v, bool structured = false)
        : name(n), structured(structured) {
        body.emplace<ExpressionAnnotation>(new StringLiteral(v));
    }
    //  : Annotation(n, { new StringLiteral(v) }, structured) { }

    static const cstring nameAnnotation;  /// Indicates the control-plane name.
    static const cstring tableOnlyAnnotation;  /// Action cannot be a default_action.
    static const cstring defaultOnlyAnnotation;  /// action can only be a default_action.
    static const cstring atomicAnnotation;  /// Code should be executed atomically.
    static const cstring hiddenAnnotation;  /// Object should not be exposed to the control-plane.
    static const cstring lengthAnnotation;  /// P4-14 annotation for varbit fields.
    static const cstring maxLengthAnnotation;  /// P4-14 annotation for varbit fields.
    static const cstring optionalAnnotation;  /// Optional parameter annotation
    static const cstring pkginfoAnnotation;  /// Package documentation annotation.
    static const cstring deprecatedAnnotation;  /// Deprecation annotation.
    static const cstring synchronousAnnotation;  /// Synchronous annotation.
    static const cstring pureAnnotation;  /// extern function/method annotation.
    static const cstring noSideEffectsAnnotation;  /// extern function/method annotation.
    static const cstring noWarnAnnotation;  /// noWarn annotation.
    static const cstring matchAnnotation;  /// Match annotation (for value sets).
    static const cstring fieldListAnnotation;  /// Used for recirculate, etc.
    static const cstring debugLoggingAnnotation;  /// Used by compiler implementer to limit debug log to the annotated IR context.
    static const cstring disableOptimizationAnnotation; /// annotation to disable certain optimization

    toString{ return absl::StrCat("@", name); }
    validate{
        BUG_CHECK(!name.name.isNullOrEmpty(), "empty annotation name");
    }

    /// Extracts name value from a name annotation
    cstring getName() const;
    /// Extracts a single string argument; error if the argument is not a string
    cstring getSingleString() const;
    /// Whether the annotation body needs to be parsed.
    inline bool needsParsing() const {
        return std::holds_alternative<UnparsedAnnotation>(body);
    }

    enum class Kind {
        Unparsed,
        Unstructured,
        StructuredKVList,
        StructuredExpressionList
    }

    inline Kind annotationKind() const {
        if (needsParsing())
            return Kind::Unparsed;
        if (!structured)
            return Kind::Unstructured;
        if (std::holds_alternative<ExpressionAnnotation>(body))
            return Kind::StructuredExpressionList;
        if (std::holds_alternative<KVAnnotation>(body))
            return Kind::StructuredKVList;

        BUG("Invalid annotation kind");
    }

    // index = 0: An unparsed annotation body
    // index = 1: Annotations that are simple expressions
    // index = 2: Annotations described as key-value pairs
    variant<Vector<AnnotationToken>,
            Vector<Expression>,
            IndexedVector<NamedExpression>> body;

    inline auto &getUnparsed() {
        try {
            return std::get<UnparsedAnnotation>(body);
        } catch (const std::bad_variant_access &) {
            BUG("Annotation has been parsed already.");
        }
    }
    inline const auto &getUnparsed() const {
        try {
            return std::get<UnparsedAnnotation>(body);
        } catch (const std::bad_variant_access &) {
            BUG("Annotation has been parsed already.");
        }
    }
    inline auto &getExpr() {
        try {
            return std::get<ExpressionAnnotation>(body);
        } catch (const std::bad_variant_access &) {
            BUG("Annotation does not contain an expression list.");
        }
     }
    inline const auto &getExpr() const {
        try {
            return std::get<ExpressionAnnotation>(body);
        } catch (const std::bad_variant_access &) {
            BUG("Annotation does not contain an expression list.");
        }
    }
    inline Expression getExpr(size_t idx) const {
        try {
            const auto &expr = getExpr();
            return expr[idx];
        } catch (const std::out_of_range &) {
            BUG("invalid annotation expression index");
        } catch (const std::bad_variant_access &) {
            BUG("Annotation does not contain an expression list.");
        }
    }
    inline auto &getKV() {
        try {
            return std::get<KVAnnotation>(body);
        } catch (const std::bad_variant_access &) {
            BUG("Annotation does not contain a key-value list.");
        }
    }
    inline const auto &getKV() const {
        try {
            return std::get<KVAnnotation>(body);
        } catch (const std::bad_variant_access &) {
            BUG("Annotation does not contain a key-value list.");
        }
    }

    /// If this is true this is a structured annotation, and there are some
    /// constraints on its contents.
    bool structured;
}

/// Implemented by all objects that can have annotations
// FIXME: Use CRTP to get access to the fields directly
interface IAnnotated {
    virtual const Vector<Annotation> &getAnnotations() const = 0;
    virtual Vector<Annotation> &getAnnotations() = 0;

    inline Annotation getAnnotation(cstring name) const {
        const auto &annotations = getAnnotations();
        return get(annotations, name);
    }
    /// Checks if there is annotation @name
    inline bool hasAnnotation(cstring name) const {
        const auto &annotations = getAnnotations();
        return get(annotations, name) != nullptr;
    }
    /// Checks if there is annotation @name and it is the only annotation on the
    /// node
    inline bool hasOnlyAnnotation(cstring name) const {
        const auto &annotations = getAnnotations();
        return annotations.size() == 1 && get(annotations, name) != nullptr;
    }
    /// Check if there are any annotations
    inline bool hasAnnotations() const {
        const auto &annotations = getAnnotations();
        return !annotations.empty();
    }
    inline void addAnnotation(Annotation annot) {
        auto &annotations = getAnnotations();
        annotations.push_back(annot);
    }
    inline void addAnnotation(cstring name, Expression expr, bool structured = false) {
        addAnnotation(new Annotation(name, { expr }, structured));
    }

    /// Add annotation if another annotation with the same name is not
    /// already present.
    inline void addAnnotationIfNew(cstring name, Expression expr, bool structured = false) {
        Annotations::addIfNew(getAnnotations(), name, expr, structured);
    }

    inline void addAnnotationIfNew(Annotation ann) {
        Annotations::addIfNew(getAnnotations(), ann);
    }

    /// If annotations with the same name are already present, remove them;
    /// add this annotation.
    inline void addOrReplaceAnnotation(cstring name, Expression expr, bool structured = false) {
        Annotations::addOrReplace(getAnnotations(), name, expr, structured);
    }

    inline void addOrReplaceAnnotation(Annotation ann) {
        Annotations::addOrReplace(getAnnotations(), ann);
    }
}

interface IInstance {
    virtual ID Name() const = 0;
    virtual Type getType() const = 0;
}

/// An argument to a function call (or constructor call)
/// Arguments may have optional names
class Argument {
    /// If an argument has no name the name.name is nullptr.
    optional ID name;
    Expression expression;
    Argument { if (!srcInfo && expression) srcInfo = expression->srcInfo; }
    dbprint { out << (name.name.isNullOrEmpty() ? "" : name.name + " = ") << expression; }
    validate { CHECK_NULL(expression); }
    toString{
        std::string result = "";
        if (!name.name.isNullOrEmpty())
            absl::StrAppend(&result, name, " = ");
        return absl::StrCat(result, expression);
    }
}

/** @} *//* end group irdefs */
